#include <map>
#include <string>

#include <endian.h>

#include "helper.hpp"
#include "serial.hpp"
#include "interface.hpp"
#include "fingerprint.hpp"

extern std::map<int, User> users;
extern xmlpp::DomParser user_xml_doc;
extern std::map<unsigned int, Base*> gids;
extern std::map<std::string, Serial> port_map;

void add_user(std::string user_name, std::string id, std::string level)
{
    User u;
    xmlpp::Document* doc = user_xml_doc.get_document();
    xmlpp::Node* node = doc->get_root_node();

    xmlpp::Element *elem = node->add_child("user");
    elem->set_attribute("name", user_name);
    elem->set_attribute("id", id);
    elem->set_attribute("level", level);

    u.id = id;
    u.level = level;
    u.name = user_name;
    u.level_attribute = elem->get_attribute("level");
    users[atoi(id.c_str())] = u;

    xmlpp::Element* nodeElement = dynamic_cast<xmlpp::Element*>(node);
    nodeElement->add_child_text("\n");

    doc->write_to_file_formatted("user_access.xml");
}

void remove_user(std::string id)
{
    bool removed = false;
    xmlpp::Document* doc = user_xml_doc.get_document();
    xmlpp::Node* node = doc->get_root_node();

    xmlpp::Node::NodeList node_list;
    xmlpp::Node::NodeList::iterator iter;

    node_list = node->get_children();
    for(iter = node_list.begin(); iter != node_list.end(); ++iter) {
        const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(*iter);
        if(removed) {
            node->remove_child(*iter);
            removed = false;
            continue;
        }
        if(nodeElement) {
            const xmlpp::Attribute* attribute;
            attribute = nodeElement->get_attribute("id");
            if(attribute && attribute->get_value() == id) {
                node->remove_child(*iter);
                removed = true;
                continue;
            }
        }
    }

    doc->write_to_file_formatted("user_access.xml");
}

const char* fp_code_to_str(int code)
{
    switch(code) {
        case CHECK_ID               : return "Check if the requested user ID exists.";
        case ADD_NEW                : return "Adding more fingerprints to a current existing user ID.";
        case CONTINUE               : return "There is more data to be sent.";
        case AUTO_ID                : return "Automatically assign user ID in enrollment.";
        case CHECK_FINGER           : return "Check if the finger is already enrolled.";
        case CHECK_FINGER_AUTO_ID   : return "Check if the finger is already enrolled. If not, assign user ID automatically.";
        case ADD_DURESS             : return "Add duress fingerprints to an existing user ID.";
        case SUCCESS                : return "Successfully completed.";
        case SCAN_SUCCESS           : return "Fingerprint input has succeeded.";
        case SCAN_FAIL              : return "Sensor or fingerprint input has failed.";
        case NOT_FOUND              : return "There is no requested data found.";
        case NOT_MATCH              : return "Fingerprint does not match.";
        case TRY_AGAIN              : return "Fingerprint image is not good.";
        case TIME_OUT               : return "Timeout for fingerprint input.";
        case MEM_FULL               : return "Maximum template capacity exceeded.";
        case EXIST_ID               : return "The requested user ID exists.";
        case FINGER_LIMIT           : return "The number of fingerprints enrolled in same ID exceeds its limit (10).";
        case UNSUPPORTED            : return "The command is not supported.";
        case INVALID_ID             : return "The requested user ID is invalid or missing.";
        case TIMEOUT_MATCH          : return "Timeout for matching in identification.";
        case BUSY                   : return "Module is processing another command.";
        case CANCELED               : return "The command is canceled.";
        case DATA_ERROR             : return "The checksum of a data packet is incorrect.";
        case DATA_OK                : return "The checksum of a data packet is correct.";
        case EXIST_FINGER           : return "The finger is already enrolled.";
        case DURESS_FINGER          : return "A duress finger is detected.";
        case LOCKED                 : return "Module is locked.";
        case REJECTED_ID            : return "Authentication mode of the user is AUTH_REJECT or the ID is in the blacklist.";
        case ENTRANCE_LIMIT         : return "Authentication fails since the entrance limit is exceeded.";
        default: return "UNKNOWN CODE";
    }
}

static uint8_t fp_calc_csum(const fp_packet_t pkt)
{
    uint8_t csum = 
        (pkt.start_code + 
         pkt.terminal_id[0] + 
         pkt.terminal_id[1] + 
         pkt.command + 
         pkt.param[0] + 
         pkt.param[1] + 
         pkt.param[2] + 
         pkt.param[3] + 
         pkt.size[0] + 
         pkt.size[1] + 
         pkt.size[2] + 
         pkt.size[3] + 
         pkt.flag_error) % 0x100;

        return csum;
}

/*
 * initialize fingerprint module 
 * send broadcast msg for setting terminal_id = 1
 * setting 'Send Scan Success' system parameter to 0x30 (No SCAN_SUCCESS) 
 * 0x41 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x31 0x00 0x00 0x00 0x83
 */
int fp_init()
{
    fp_packet_t req, resp;

    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x00;
    req.terminal_id[1]  = 0x00;
    req.command         = 0x01;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x01;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x6D;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    fp_send_bcast_msg(req);

    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x01;
    req.terminal_id[1]  = 0x00;
    req.command         = 0x01;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x30;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x75;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error != SUCCESS) {
        logger(SM_LOG_ERR,"FINGERPRINT: %s", fp_code_to_str(resp.flag_error));
        return -1;
    }
    logger(SM_LOG_INFO, "[FINGERPRINT] ---> [%s]\n", fp_code_to_str(resp.flag_error));

    return 0;
}

/* enroll new finger and return it's id or -1 in case of failure*/
int fp_add_user(std::string name, std::string level) 
{
    int id, rv = -1;
    fp_packet_t req, resp;
    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x01;
    req.terminal_id[1]  = 0x00;
    req.command         = 0x05;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x00;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = AUTO_ID;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error == SUCCESS) {
        rv = id = le32toh(*((uint32_t*)resp.param));
        add_user(name, to_string(id), level);
    }
    logger(SM_LOG_INFO, "[FINGERPRINT] ---> [%s]\n", fp_code_to_str(resp.flag_error));

    return rv;
}

/*
 * turn identification mode and wait for scan and verification, return id or -1
 * in case of failure
 * 0x41 0x01 0x0 0x11 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 */
int fp_identify_user_by_scan()
{
    int rv = -1;
    fp_packet_t req, resp;
    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x01;
    req.terminal_id[1]  = 0x00;
    req.command         = 0x11;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x00;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x00;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error == SUCCESS) {
        rv = le32toh(*((uint32_t*)resp.param));
    }
    logger(SM_LOG_INFO, "[FINGERPRINT] ---> [%s]\n", fp_code_to_str(resp.flag_error));

    return rv;
}

int fp_delete_all_users()
{
    int rv = -1;
    fp_packet_t req, resp;
    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x01;
    req.terminal_id[1]  = 0x00;
    req.command         = 0x17;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x00;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x00;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error == SUCCESS) {
        rv = 1;
        // remove all users from user_access.xml file.
        for(std::map<int, User>::iterator it = users.begin(); it != users.end(); ++it)
            remove_user(to_string(it->first));
        users.clear();
    }

    return rv;
}

int fp_delete_user_by_id(std::string id)
{
    int rv = -1;
    fp_packet_t req, resp;
    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x01;
    req.terminal_id[1]  = 0x00;
    req.command         = 0x16;

    *((uint32_t*)req.param) = htole32((uint32_t)atoi(id.c_str()));

    req.size[0]         = 0x00;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x00;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error == SUCCESS) {
        rv = le32toh(*((uint32_t*)resp.param));
        remove_user(id);
        for(std::map<int, User>::iterator it = users.begin(); it != users.end(); ++it)
            if(it->first == atoi(id.c_str()))
                users.erase(it);
    } else {
        logger(SM_LOG_ERR,"FINGERPRINT: can't delete fingerprint, [%s]", fp_code_to_str(resp.flag_error));
    }
    logger(SM_LOG_INFO, "[FINGERPRINT] ---> [%s]\n", fp_code_to_str(resp.flag_error));

    return rv;
}

/* delete enrolled template by scan, return 0 or -1 in case of failure */
int fp_delete_user_by_scan()
{
    int id, rv = -1;
    fp_packet_t req, resp;
    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x01;
    req.terminal_id[1]  = 0x00;
    req.command         = 0x1E;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x00;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x00;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error == SUCCESS) {
        id = rv = le32toh(*((uint32_t*)resp.param));
        remove_user(to_string(rv));
        for(std::map<int, User>::iterator it = users.begin(); it != users.end(); ++it)
            if(it->first == id)
                users.erase(it);
    } else {
        logger(SM_LOG_ERR,"FINGERPRINT: can't delete fingerprint, [%s]", fp_code_to_str(resp.flag_error));
    }
    logger(SM_LOG_INFO, "[FINGERPRINT] ---> [%s]\n", fp_code_to_str(resp.flag_error));

    return rv;
}

/* change template level, return 0 or -1 in case of failure */
int fp_change_user_level(std::string id, std::string level)
{
    int gid, rv = -1;
    std::map<int, User>::iterator u_it;

    gid = atoi(id.c_str());
    u_it = users.find(gid);
    if(u_it == users.end()){
        logger(SM_LOG_ERR, "ERROR, no such user ID=[%s]", id.c_str());
        return rv;
    }

    rv = 1;
    users[gid].level_attribute->set_value(level);

    return rv;
}
